/*
 *   项目名称：rpc-demo
 *   文件名称：com.xiaolon.rpc.proxy.ServiceProxy
 *
 *   创建人：  ZHOU XIAO LONG
 *   创建日期：2024/12/3
 *
 */
package com.xiaolon.rpc.proxy;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.xiaolon.rpc.RpcApplication;
import com.xiaolon.rpc.config.RegistryConfig;
import com.xiaolon.rpc.config.RpcConfig;
import com.xiaolon.rpc.constant.RpcConstant;
import com.xiaolon.rpc.fault.retry.RetryStrategy;
import com.xiaolon.rpc.fault.retry.RetryStrategyFactory;
import com.xiaolon.rpc.fault.tolerant.TolerantStrategy;
import com.xiaolon.rpc.fault.tolerant.TolerantStrategyFactory;
import com.xiaolon.rpc.loadbalancer.LoadBalancer;
import com.xiaolon.rpc.loadbalancer.LoadBalancerFactory;
import com.xiaolon.rpc.model.RpcRequest;
import com.xiaolon.rpc.model.RpcResponse;
import com.xiaolon.rpc.model.ServiceMetaInfo;
import com.xiaolon.rpc.registry.Registry;
import com.xiaolon.rpc.registry.RegistryFactory;
import com.xiaolon.rpc.serializer.JdkSerializer;
import com.xiaolon.rpc.serializer.Serializer;
import com.xiaolon.rpc.serializer.SerializerFactory;
import com.xiaolon.rpc.server.tcp.VertxTcpClient;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/*
 * 类描述：ServiceProxy
 * 调用代理
 */
public class ServiceProxy implements InvocationHandler {
    /**
     * 调用代理
     *
     * @param proxy  the proxy instance that the method was invoked on
     * @param method the {@code Method} instance corresponding to
     *               the interface method invoked on the proxy instance.  The declaring
     *               class of the {@code Method} object will be the interface that
     *               the method was declared in, which may be a superinterface of the
     *               proxy interface that the proxy class inherits the method through.
     * @param args   an array of objects containing the values of the
     *               arguments passed in the method invocation on the proxy instance,
     *               or {@code null} if interface method takes no arguments.
     *               Arguments of primitive types are wrapped in instances of the
     *               appropriate primitive wrapper class, such as
     *               {@code java.lang.Integer} or {@code java.lang.Boolean}.
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 通过工厂调用配置指定的序列化器
        Serializer serializer = SerializerFactory.getInstance(RpcApplication.getRpcConfig().getSerializer());

        // 构造请求
        String serviceName = method.getDeclaringClass().getName();
        RpcRequest rpcRequest = RpcRequest.builder()
                .serviceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .args(args)
                .build();

        try {
            // 序列化
            byte[] bodyBytes = serializer.serialize(rpcRequest);

            // 从注册中心获取服务提供者请求地址
            RpcConfig rpcConfig = RpcApplication.getRpcConfig();
            Registry registry = RegistryFactory.getInstance(rpcConfig.getRegistryConfig().getRegistry());
            ServiceMetaInfo serviceMetaInfo = new ServiceMetaInfo();
            serviceMetaInfo.setServiceName(serviceName);
            serviceMetaInfo.setServiceVersion(RpcConstant.DEFAULT_SERVICE_VERSION);
            List<ServiceMetaInfo> serviceMetaInfoList = registry.serviceDiscovery(serviceMetaInfo.getServiceKey());

            if (CollUtil.isEmpty(serviceMetaInfoList)) {
                throw new RuntimeException("暂无服务地址");
            }

            // 负载均衡
//            // 选择服务节点
//            ServiceMetaInfo selectedServiceMetaInfo = serviceMetaInfoList.get(0);
            // 改进
            // 使用负载均衡器选择节点
            LoadBalancer loadBalancer = LoadBalancerFactory.getInstance(rpcConfig.getLoadBalancer());
            // 讲调用方法名（请求路径）作为负载均衡参数
            Map<String, Object> reuestParams = new HashMap<>();
            reuestParams.put("methodName", rpcRequest.getMethodName());
            ServiceMetaInfo selectedServiceMetaInfo = loadBalancer.select(reuestParams, serviceMetaInfoList);


            // 发送TCP请求
//            RpcResponse rpcResponse = VertxTcpClient.doResponse(rpcRequest, selectedServiceMetaInfo);
            // 改进：使用重试机制
//            RetryStrategy retryStrategy = RetryStrategyFactory.getInstance(rpcConfig.getRetryStrategy());
//            RpcResponse rpcResponse = retryStrategy.doRetry(() ->
//                    VertxTcpClient.doResponse(rpcRequest, selectedServiceMetaInfo)
//            );
            // 进一步改进：增加容错机制
            RpcResponse rpcResponse;
            try {
                RetryStrategy retryStrategy = RetryStrategyFactory.getInstance(rpcConfig.getRetryStrategy());
                rpcResponse = retryStrategy.doRetry(() ->
                        VertxTcpClient.doResponse(rpcRequest, selectedServiceMetaInfo));
            } catch (Exception e) {
                // 重试机制
                TolerantStrategy tolerantStrategy = TolerantStrategyFactory.getInstance(rpcConfig.getTolerantStrategy());
                rpcResponse = tolerantStrategy.doTolerant(null, e);
            }
            return rpcResponse.getData();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
